#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/neutrino.h>
#include <sys/dispatch.h>


#include "Response.h"
#include "Log.h"
#include "Connector.h"

Connector * Connector::connector = NULL;


Connector::Connector() {
	debug1("Connector() constructor");
}

Connector::~Connector() {
	//need to join with all threads
	for(std::list<pthread_t*>::iterator i = threadList.begin(); i != threadList.end(); i++){
		debug1("join thread");
		pthread_join(**i, NULL);
	}	
}


Connector * Connector::GetInstance() {
	if (connector == NULL) {
		connector = new Connector();
	}
	return connector;
}

int Connector::sendMessage(const char * serverName, Message * msg, Response * resp){
	int server_coid, self_coid, chid;
	
	if(resp != NULL){
		debug2("resp=",(int)resp);
		debug2("status=",resp->status);
	}
	
	setvbuf (stdout, NULL, _IOLBF, 0);
	debug1("Try open connection to server");
	
	
	/* look for server */
	server_coid = name_open(serverName, NAME_FLAG_ATTACH_GLOBAL);
	
	if (server_coid == -1) {
		debug2("Could not open connection to server ", serverName);
	}
	
	chid = ChannelCreate(0);
	if (-1 == chid) {
		debug1("Cannot create channel");
		return -1;
	}
	self_coid = ConnectAttach(0, 0, chid, _NTO_SIDE_CHANNEL, 0);
	if (-1 == self_coid) {
		debug1("ConnectAttach failed");
		return -1;
	}

	/* Initialize the sigevent structure (msg.ev) in the message 
	 * to be sent to the server.
	 */
//	SIGEV_PULSE_INIT(&msg.ev, self_coid, getprio(0), MY_PULSE_CODE, 0);
	
	//TODO Liso: need to add reply message struct
	//ex. MsgSend(server_coid, &msg, sizeof( msg),replyMsg, sizeof(replyMsg)
	int res;

	if(resp != NULL){
		res = MsgSend_r(server_coid, msg, sizeof( Message ), resp, sizeof(Response) );
	}
	else{
		res = MsgSend_r(server_coid, msg, sizeof( Message ), NULL, 0 );
	}
	if (res < 0)
	{
		debug1("MsgSend failure");
		return res;
	}

	debug2("Send successful result=",res);
	if(resp != NULL){
		debug4("status=",resp->status, " response=", resp->response);
	}
	
	debug1("END");
	return 0;
}

////TODO: to remove cause think its unused!
//int Connector::getResource(int object, int quantity, Response * resp) {
//	Message * msg = new Message();
//	
//	msg->type = Message::TYPE_GET;
//	msg->object1 = object;
//	msg->param1 = quantity;
//	int res = this->sendMessage(SERVER_NAME, msg, resp);
//	delete msg;
//	return res;
//}

int Connector::getInfo(int object, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_GET_INFO;
	msg->object1 = object;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::getInfo(int object1, int object2, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_GET_INFO;
	msg->object1 = object1;
	msg->object2 = object2;
	
	debug2("status ",resp->status);
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::add(int object, int quantity, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_ADD;
	msg->object1 = object;
	msg->param1 = quantity;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::add(int object1, int object2, int quantity, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_ADD;
	msg->object1 = object1;
	msg->object2 = object2;
	msg->param1 = quantity;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::take(int object, int quantity, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_GET;
	msg->object1 = object;
	msg->param1 = quantity;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::take(int object1, int object2, int quantity, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_GET;
	msg->object1 = object1;
	msg->object2 = object2;
	msg->param1 = quantity;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::setRelation(int object1, int object2, int cardinality, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_NEW;
	msg->object1 = object1;
	msg->object2 = object2;
	msg->param1 = cardinality;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

int Connector::dropRelation(int object1, int object2, Response * resp) {
	Message * msg = new Message();
	
	msg->type = Message::TYPE_DELETE;
	msg->object1 = object1;
	msg->object2 = object2;
	
	int res = this->sendMessage(SERVER_NAME, msg, resp);
	delete msg;
	return res;	
}

void Connector::runServer() {
	startListening(SERVER_NAME);
}

void Connector::runClient(const char * clientName) {
	this->startListening(clientName);
}

void Connector::setMessageListener(MessageListener * messageListener) {
	printf("setMessageListener(MessageListener * messageListener)\n");
	this->messageListener = messageListener;
}

int Connector::startListening(const char * name) {
	printf("startListening(const char * name) {\n");
	name_attach_t *att;
	int rcvid;
	struct _msg_info msg_info;

	setvbuf (stdout, NULL, _IOLBF, 0);

	printf("before name_attach | name=%s\n", name);
	
	att = name_attach(NULL, name, NAME_FLAG_ATTACH_GLOBAL);
	
	printf("after name_attach\n");
	if (NULL == att) {
		perror("name_attach() error\n");
		return EXIT_FAILURE;
	}
	
	RecvMsgs buffer;
	
	// main receive loop
	while (1) {
		printf("MsgReceive()\n");
		rcvid = MsgReceive_r(att->chid, &buffer, sizeof(buffer), &msg_info);
		if ( rcvid < 0 ) {
			debug2("MsgReceive failed with error: ", rcvid);
			continue;
		}
		printf("Message received\n");
		if ( 0 == rcvid) {
			/* we received a pulse, often a side-effect of using name_attach()
			 * we need to deal with the system pulses appropriately 
			 */
			switch (buffer.pulse.code) {
			/* system disconnect pulse */
			case _PULSE_CODE_DISCONNECT:
				/* always do the ConnectDetach(), though */
				ConnectDetach(buffer.pulse.scoid);
				printf("disconnect from a client %X\n", buffer.pulse.scoid);
				break;
				/* our pulse */
			default:
				printf("unexpect pulse code: %X\n", buffer.pulse.code );
				break;
			}
			continue;
		}
		/* not an error, not a pulse, therefor a message */
		if (buffer.type == _IO_CONNECT) {
			/* _IO_CONNECT because someone did a name_open() to us, must EOK it. */
			int res = MsgReply_r(rcvid, EOK, NULL, 0);
			if(res < 0){
				debug2 ("MsgReply failed with error: ", res);
			}
			continue;
		}
		if (buffer.type > _IO_BASE && buffer.type <= _IO_MAX) {
			/* unexpected system message, may be side effect of name_attach() */
			MsgError(rcvid, ENOSYS);
			continue;
		}
		// messages
		buffer.client_msg.reserved = rcvid;
		int res = deliverMessage(buffer.client_msg);
		if(res < 0){
			debug2("deliverMessage failed with error: ", res);
		}
	}
	return 0;
}

void * Connector::Run(void * arg) {
	Message * msg = (Message *)arg;
	GetInstance()->getMessageListener()->messageListener(msg);
	delete msg;
	return NULL;
}

int Connector::deliverMessage(Message& msg) {
	pthread_t * thread = new pthread_t();
	Message * message = new Message(); //deleted in Connector::Run
	*message = msg; 
	int res = pthread_create( thread, NULL, &Connector::Run, message);
	if(res < 0){
		debug2("pthread_create failed with error: ", res);
		return res;
	}
	
	addThread(thread);
	
	return EOK;
}

MessageListener * Connector::getMessageListener() {
	return this->messageListener;
}

int Connector::build(int objectType){
	debug1("Call to unimplemented method: Connector::build(int objectType)");
	return 0;
}

int Connector::ping(){
	debug1("Call to unimplemented method: Connector::ping()");
	return 0;
}

void Connector::addThread(pthread_t * thread){
	threadList.push_back(thread);
}
